﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using log4net;
using log4net.Config;
using log4net.Core;
using NDesk.Options;

namespace FitNesse4Net
{
    class Program
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(Program));

        static void Main(string[] args)
        {
            //new System.Windows.Forms.Form().ShowDialog();

            var port = 80;
            var workDirectory = Path.GetFullPath(".");
            var rootDirectoryName = "FitNesseRoot";
            var logDirectory = "";
            var expireDays = 14;
            var disableUpgrade = false;
            var testToRun = "";
            var testSuiteToRun = "";
            var shutdown = false;
            var targetDirectory = "target";
            var testRunner = false;
            var logLevel = "INFO";

            if (args.Length == 0)
            {
                ShowHelpAndExit();
            }

            var optionSet = new OptionSet
                                {
                                    { "h|?|help", v => ShowHelpAndExit() },
                                    { "port=", v => port = int.Parse(v) },
                                    { "work-directory=", v => workDirectory = Path.GetFullPath(v) },
                                    { "root-directory-name=", v => rootDirectoryName = Path.GetFullPath(v) },
                                    { "log-directory=", v => logDirectory = Path.GetFullPath(v) },
                                    { "expire-days=", v => expireDays = int.Parse(v) },
                                    { "disable-upgrade=", v => disableUpgrade = bool.Parse(v) },
                                    { "run-test=", v => testToRun = v },
                                    { "run-test-suite=", v => testSuiteToRun = v },
                                    { "target-directory=", v => targetDirectory = Path.GetFullPath(v) },
                                    { "shutdown", v => shutdown = true },
                                    { "test-runner", v => testRunner = true },
                                    { "log-level=", v => logLevel = v },
                                };

            optionSet.Parse(args);

            BasicConfigurator.Configure(); // log4net
            
            SetLoggingLevel(logLevel);

            IFitNesse fitNesse = new FitNesse { WorkDirectoryPath = workDirectory, TargetDirectory = targetDirectory };

            // run fitnesse as test runner
            if(testRunner)
            {
                // initialize assemblies
                fitNesse.StartTestRunner(args);

                return;
            }

            // stop server
            if (shutdown)
            {
                if (port == 80)
                {
                    log.Warn("Assuming FitNesse server is running on port 80, if it is running on another server - specify it with --port=<port> argument");
                }
                fitNesse.StopServer(port);
                return;
            }

            // run a single test
            if (testToRun.Length > 0)
            {
                fitNesse.RunTest(testToRun);
                return;
            }

            // run a test suite
            if (testSuiteToRun.Length > 0)
            {
                fitNesse.RunTestSuite(testSuiteToRun);
                return;
            }

            // otherwise run fitnesse using it's standard arguments
            var defaultArgs = new List<string>
                                  {
                                      "-p", port.ToString(),
                                      "-d", workDirectory,
                                      "-r", rootDirectoryName,
                                      "-e", expireDays.ToString()
                                  };


            if (logDirectory != "")
            {
                defaultArgs.Add("-l");
                defaultArgs.Add(logDirectory);
            }
            
            if (disableUpgrade)
            {
                defaultArgs.Add("-o");
            }

            fitNesse.ParseArgumentsAndRun(defaultArgs.ToArray());
        }

        private static void ShowHelpAndExit()
        {
            Console.Write(
                "Usage:\n" +
                "  FitNesse4Net.exe --port=<port> [OPTIONS] | --shutdown [OPTIONS] | --run-test=<test-name> [OPTIONS] | --run-test-suite=<suite name> [OPTIONS] | [OPTIONS]\n" +
                "                                                   \n" +
                "    --help                             prints this help\n" +
                "    --port=<port>                      starts FitNesse server using a given port. The default is 80\n" +
                "    --work-directory=<path>            working directory, the one where FitNesseRoot will be located\n" +
                "    --log-directory=<path>             working directory, the one where FitNesseRoot will be located\n" +
                "    --expire-days=<days>               expiration days for page versions. The default is 14. \n" +
                "                                       set it to 0 if you don't want to keep page history.\n" +
                "    --disable-upgrate=<true|false>     \n" +
                "    --run-test=<test name>             runs a single test, the name of the test should be in the \n" +
                "                                             form FrontPage.SubPage.TestName \n" +
                "    --run-test-suite=<suite name>      runs a test suite\n" +
                "    --target-directory=<dir>           directory where to store results, default is 'target'\n" +
                "    --shutdown\n" +
                "    --test-runner                      runs in test runner mode (used by server)\n" +
                "    --log-level=DEBUG|INFO|WARN|ERROR  default is INFO\n" +
                " \n"
                );

            Environment.Exit(0);
        }

        private static void SetLoggingLevel(string logLevel)
        {
            if (logLevel.ToLower().Equals(Level.Debug.ToString().ToLower()))
            {
                SetLoggingLevel(Level.Debug);
            }
            else if (logLevel.ToLower().Equals(Level.Info.ToString().ToLower()))
            {
                SetLoggingLevel(Level.Info);
            }
            else if (logLevel.ToLower().Equals(Level.Warn.ToString().ToLower()))
            {
                SetLoggingLevel(Level.Warn);
            }
            else if (logLevel.ToLower().Equals(Level.Error.ToString().ToLower()))
            {
                SetLoggingLevel(Level.Error);
            }
        }

        /// <summary>
        /// Log4net hack, allows to set logging level using API
        /// </summary>
        /// <param name="level"></param>
        public static void SetLoggingLevel(Level level)
        {
            var repositories = LogManager.GetAllRepositories();

            //Configure all loggers to be at the debug level.
            foreach (var repository in repositories)
            {
                repository.Threshold = repository.LevelMap[level.ToString()];
                var hierarchy = (log4net.Repository.Hierarchy.Hierarchy)repository;
                var loggers = hierarchy.GetCurrentLoggers();
                foreach (var logger in loggers)
                {
                    ((log4net.Repository.Hierarchy.Logger)logger).Level = hierarchy.LevelMap[level.ToString()];
                }
            }

            //Configure the root logger.
            var h = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
            var rootLogger = h.Root;
            rootLogger.Level = h.LevelMap[level.ToString()];
        }
    }
}
